package expresso_plugin.editors;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.StringReader;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.console.actions.ClearOutputAction;
import org.eclipse.ui.editors.text.FileDocumentProvider;

public class ExpressoDocumentProvider extends FileDocumentProvider {
	public static final String NEWLINE = System.getProperty("line.separator");

	private final String CONSOLE_NAME = "Expresso Transcoder";

	private MessageConsole findConsole(String name) {
		// Create a new console to replace any existing console because
		// clearConsole doesn't work.
		ConsolePlugin plugin = ConsolePlugin.getDefault();
		IConsoleManager conMan = plugin.getConsoleManager();
		IConsole[] existing = conMan.getConsoles();
		for (int i = 0; i < existing.length; i++)
			if (name.equals(existing[i].getName()))
				// TODO: is this safe?
				conMan.removeConsoles(new IConsole[] { existing[i] });

		MessageConsole myConsole = new MessageConsole(name, null);
		conMan.addConsoles(new IConsole[] { myConsole });
		return myConsole;
	}

	public ExpressoDocumentProvider() {

	}

	protected void setDocumentContent(IDocument document,
			InputStream contentStream, String encoding) throws CoreException {
		// We need to override this method instead of just
		// setupDocument(Object, IDocument) to catch updates that are caused
		// by another editor changing the same file.
		super.setDocumentContent(document, contentStream, encoding);

		String javaSource = document.get();
		String expressoSource = extract(javaSource);
		document.set(expressoSource);
	}

	protected void doSaveDocument(IProgressMonitor monitor, Object element,
			IDocument document, boolean overwrite) throws CoreException {
		String expressoSource = document.get();
		String javaSource = transcode(expressoSource);
		IDocument javaDocument = new Document(javaSource);
		super.doSaveDocument(monitor, element, javaDocument, overwrite);
	}

	private String extract(String javaSource) {
		try {
			StringReader stringReader = new StringReader(javaSource);
			BufferedReader reader = new BufferedReader(stringReader);
			StringBuffer expressoSource = new StringBuffer();

			// Skip content before @bgen
			for (;;) {
				String line = reader.readLine();
				if (line == null || line.contains("@bgen")) {
					break;
				} else {
					// Discard line
				}
			}

			// Add content between @bgen and @egen
			for (;;) {
				String line = reader.readLine();
				if (line == null || line.contains("@egen")) {
					break;
				} else {
					expressoSource.append(line);
					expressoSource.append(NEWLINE);
				}
			}
			reader.close();
			return expressoSource.toString();
		} catch (IOException ioe) {
			throw new RuntimeException(
					"IOException not expected from reading from a String.", ioe);
		}
	}

	private void handleException(Exception e) {
		MessageConsole console = findConsole(CONSOLE_NAME);
		console.activate();
		MessageConsoleStream out = console.newMessageStream();
		e.printStackTrace(new PrintStream(out));
		try {
			out.close();
		} catch (IOException ioe) {
			throw new RuntimeException("Unexpected IOException", ioe);
		}
	}

	private String transcode(String expressoSource) {
		StringBuffer javaSource = new StringBuffer();
		try {
			// TODO: this command should not be hard coded
			Runtime runtime = Runtime.getRuntime();
			Process process = runtime
					.exec(new String[] {
							"java",
							"-classpath",
							"/home/pete/workspace/expresso-compiler/bin:/opt/eclipse/plugins/org.eclipse.equinox.common_3.2.0.v20060603.jar:/opt/eclipse/plugins/org.eclipse.jdt.core_3.2.1.v_677_R32x.jar",
							"expresso.compiler.ExpressoTranscoder" });
			{
				// Write the expressoSource to standard input of transcoder.
				BufferedWriter writer = new BufferedWriter(
						new OutputStreamWriter(process.getOutputStream()));
				writer.write(expressoSource);
				writer.close();
			}
			{
				// Read data from transcoder output and write it to the
				// javaSource
				BufferedReader reader = new BufferedReader(
						new InputStreamReader(process.getInputStream()));
				for (;;) {
					int c = reader.read();
					if (c == -1) {
						break;
					} else {
						javaSource.append((char) c);
					}
				}
				reader.close();
			}
			{
				// Clear the console and write any data that appeared on the
				// transcoder's error stream.
				MessageConsole console = findConsole(CONSOLE_NAME);
				console.activate();
				BufferedReader reader = new BufferedReader(
						new InputStreamReader(process.getErrorStream()));
				MessageConsoleStream stream = console.newMessageStream();
				for (;;) {
					String line = reader.readLine();
					if (line == null) {
						break;
					} else {
						stream.println(line);
					}
				}
				stream.close();
				reader.close();
			}

			// Make sure the process is finished.
			int exitValue = process.waitFor();
		} catch (IOException ioe) {
			handleException(ioe);
		} catch (InterruptedException ie) {
			handleException(ie);
		}
		javaSource.append(NEWLINE);
		javaSource.append("/*-@bgen(expresso):");
		javaSource.append(" Generated by ExpressoTranscoder.");
		javaSource.append(" Do not edit this line.");
		javaSource.append(NEWLINE);
		javaSource.append(expressoSource);
		javaSource.append(NEWLINE);
		javaSource.append("@egen*/");
		return javaSource.toString();
	}
}